﻿using System;
using System.Web.Mvc;
using System.Web.Routing;
using Drill.Integration.Web.Mocks;
using Drill.Web.Mvc.Tests.TestHelpers;
using NUnit.Framework;

namespace Drill.Integration.Web.Mvc
{
	[TestFixture]
	public class DependencyResolverControllerFactoryTests
    {
		// NOTES
		// These test use some mock and test classes as follows:
		//
		// * MockDependencyResolver - Used to represent an IDependencyResolver instance.
		//                            Can only return instances of the MyFirstTestController class.
		//
		// * MockControllerFactory  - Used to represent a "default" or "wrapped" IControllerFactory. 
		//                            Can only return instances of the MySecondTestController class.
		//
		// * MyFirstTestContoller   - Represents an IController instance.
		// * MySecondTestContoller  - Represents an IController instance.
		// * MyThirdTestContoller   - Represents an IController instance.
		// 

		[Test]
		public void CanConstruct()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();

			// Act
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);

			// Assert
			Assert.That(controllerFactory, Is.Not.Null);
		}

		[Test]
		public void CanCreateMyFirstTestControllerWithInnerControllerFactory()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MyFirstTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			IController controller = controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			Assert.That(controller, Is.Not.Null);
			Assert.That(controller, Is.TypeOf(typeof(MyFirstTestController)));
		}

		[Test]
		public void CanCreateMySecondTestControllerWithInnerControllerFactory()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MySecondTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			IController controller = controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			Assert.That(controller, Is.Not.Null);
			Assert.That(controller, Is.TypeOf(typeof(MySecondTestController)));
		}

		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "This method cannot be called during the application's pre-start initialization phase.", MatchType = MessageMatch.Contains)]
		[Test]
		public void CannotCreateMyThirdTestControllerWithInnerControllerFactory()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MyThirdTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			// Nothing to assert because the test should throw an expected exception (see ExpectedException attribute).
		}

		[Test]
		public void CanCreateMyFirstTestControllerWithoutInnerControllerFactory()
		{
			// Arrange
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver);
			const string controllerName = "MyFirstTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			IController controller = controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			Assert.That(controller, Is.Not.Null);
			Assert.That(controller, Is.TypeOf(typeof(MyFirstTestController)));
		}

		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "This method cannot be called during the application's pre-start initialization phase.", MatchType = MessageMatch.Contains)]
		[Test]
		public void CannotCreateMySecondTestControllerWithoutInnerControllerFactory()
		{
			// Arrange
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver);
			const string controllerName = "MySecondTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			// Nothing to assert because the test should throw an expected exception (see ExpectedException attribute).
		}

		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "This method cannot be called during the application's pre-start initialization phase.", MatchType = MessageMatch.Contains)]
		[Test]
		public void CannotCreateMyThirdTestControllerWithoutInnerControllerFactory()
		{
			// Arrange
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver);
			const string controllerName = "MyThirdTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			// Nothing to assert because the test should throw an expected exception (see ExpectedException attribute).
		}

		[Test]
		public void CanCreateMyFirstTestControllerWithInnerControllerFactoryMultipleCalls()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MyFirstTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			for (int i = 0; i < 10; i++)
			{
				// Act
				IController controller = controllerFactory.CreateController(requestContext, controllerName);

				// Assert
				Assert.That(controller, Is.Not.Null);
				Assert.That(controller, Is.TypeOf(typeof(MyFirstTestController)));
			}
		}

		[Test]
		public void CanCreateMySecondTestControllerWithInnerControllerFactoryMultipleCalls()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MySecondTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			for (int i = 0; i < 10; i++)
			{
				// Act
				IController controller = controllerFactory.CreateController(requestContext, controllerName);

				// Assert
				Assert.That(controller, Is.Not.Null);
				Assert.That(controller, Is.TypeOf(typeof(MySecondTestController)));
			}
		}

		[ExpectedException(typeof(ArgumentNullException), ExpectedMessage = "dependencyResolver", MatchType = MessageMatch.Contains)]
		[Test]
		public void CannotConstructWithNullDependencyResolverArgument()
		{
			// Arrange
			IDependencyResolver dependencyResolver = null;

			// Act
			// ReSharper disable ExpressionIsAlwaysNull
			// ReSharper disable ObjectCreationAsStatement
			new DependencyResolverControllerFactory(dependencyResolver);
			// ReSharper restore ObjectCreationAsStatement
			// ReSharper restore ExpressionIsAlwaysNull

			// Assert
			// Nothing to assert because the test should throw an expected exception (see ExpectedException attribute).
		}

		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "This method cannot be called during the application's pre-start initialization phase.", MatchType = MessageMatch.Contains)]
		[Test]
		public void CannotCreateMyLastTestControllerWhichThrowsAnExceptionDuringResolution()
		{
			// Arrange
			IControllerFactory innerControllerFactory = new MockControllerFactory();
			IDependencyResolver dependencyResolver = new MockDependencyResolver();
			IControllerFactory controllerFactory = new DependencyResolverControllerFactory(dependencyResolver, innerControllerFactory);
			const string controllerName = "MyLastTest";
			RequestContext requestContext = new RequestContext(new HttpContextMock(), new RouteData());

			// Act
			controllerFactory.CreateController(requestContext, controllerName);

			// Assert
			// Nothing to assert because the test should throw an expected exception (see ExpectedException attribute).
		}
    }
}
